package notifiers;

import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.calendar.CalendarEntry;
import com.google.gdata.data.calendar.CalendarEventEntry;
import com.google.gdata.data.calendar.CalendarFeed;
import com.google.gdata.data.extensions.Reminder;
import com.google.gdata.data.extensions.Reminder.Method;
import com.google.gdata.data.extensions.When;
import com.google.gdata.util.ServiceException;
import java.io.IOException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import rsshunter.Encoder;

/**
 * SmsSender is handling sending SMS messages using Google Calendar. It needs
 * access to gmail account with activated SMS notifications.
 */
public class SmsSender {

    private String username = "";
    private String password = "";
    private String calendarName = "Notifications";
    private boolean calendarNameSpecified = false;
    private Long timeZoneOffset = 2L;
    private Long eventStartOffset = 7L;
    private Long eventEndOffset = 12L;

    public SmsSender() {
        timeZoneOffset = timeZoneOffset * 60 * 60 * 1000;
        eventStartOffset = eventStartOffset * 60 * 1000;
        eventEndOffset = eventEndOffset * 60 * 1000;
    }

    /**
     * Set Gmail username and password.
     */
    public void setAuthentication(String username, String password) {
        this.username = username;
        try {
            this.password = Encoder.decrypt(password);
        } catch (GeneralSecurityException ex) {
            System.err.println(
                    "Error decrypting password to MailSender: " + ex);
        } catch (IOException ex) {
            System.err.println(
                    "Error decrypting password to MailSender: " + ex);
        }
    }

    /**
     * Prepare SMS notification for Rss-type base.
     *
     * @param channelsMap Map with name of channel and number of new feeds.
     * @param baseName Name of the base.
     * @return String with composed sms message.
     */
    public String composeRssNotification(
            Map<String, Integer> channelsMap, String baseName) {
        StringBuilder message = new StringBuilder("RSSHunter - you've got ");
        int entriesNumber = 0;
        for (String key : channelsMap.keySet()) {
            entriesNumber += channelsMap.get(key);
        }
        message.append(Integer.toString(entriesNumber));
        message.append(" new entries at base ");
        message.append(cutString(baseName, 10));
        message.append(".");
        return message.toString();
    }

    /**
     * Prepare SMS notification for Rss-type base.
     *
     * @param newContent List with uris of pages with new content.
     * @param baseName Name of the base.
     * @return String with composed sms message.
     */
    public String composeHtmlNotification(
            List<String> newContent, String baseName) {
        StringBuilder message = new StringBuilder();
        if (newContent.size() == 1) {
            String uri = newContent.get(0);
            uri = uri.substring(7); // delete "http://" (every letter in sms matters)
            message.append("RSSHunter - the content of site ");
            message.append(cutString(uri, 14));
            message.append(" has changed.");

        } else {
            message.append("RSSHunter - the content of ");
            message.append(newContent.size());
            message.append(" pages in base ");
            message.append(cutString(baseName, 10));
            message.append(" has changed.");
        }
        return message.toString();
    }

    /**
     * Send sms message. Proper username and password must be set. It also need
     * adresee customised with setTo() method.
     *
     * @param text Sms message, up to 60 signs.
     * @throws ServiceException Usually bad username or password.
     */
    public void sendSMS(String text) throws ServiceException {
        try {

            // Create a new Calendar service
            CalendarService myService = new CalendarService(
                    "GCal Event Notifier");
            myService.setUserCredentials(username, password);

            // Get a list of all entries
            URL metafeedUrl = new URL(
                    "http://www.google.com/calendar/feeds/default/allcalendars/full");

            CalendarFeed resultFeed = myService.getFeed(metafeedUrl,
                    CalendarFeed.class);
            List entries = resultFeed.getEntries();
            CalendarEntry entry =
                    (calendarNameSpecified) ? findCalendar(entries)
                    : (CalendarEntry) entries.get(0);

            sendDowntimeAlert(myService, entry, text, "",
                    eventStartOffset, eventEndOffset, timeZoneOffset);
        } catch (IOException e) {
            System.err.println("IO error while sending SMS: " + e);
        }
    }

    public void setCalendarName(String calendarName) {
        this.calendarName = calendarName;
    }

    public String getCalendarName() {
        return calendarName;
    }

    /**
     * Cut string if it's too long for sms message.
     *
     * @param toCut String to be cut.
     * @param maxLength Max length of given string.
     * @return String with length lesser or equal than maxLength.
     */
    private String cutString(String toCut, int maxLength) {
        if (toCut.length() > maxLength) {
            return toCut.substring(0, maxLength - 2) + "..";
        }
        return toCut;
    }

    /**
     * Find calendar set in calendarName field. If calendar is not found, it
     * sets first callendar from account.
     *
     * @param entries Google calendar entries.
     * @return Entry containing calendar.
     */
    private CalendarEntry findCalendar(List entries) {
        for (int i = 0; i < entries.size(); i++) {
            CalendarEntry entry = (CalendarEntry) entries.get(i);
            String currCalendarName = entry.getTitle().getPlainText();

            if (currCalendarName.equals(calendarName)) {
                return entry;
            }
        }
        return (CalendarEntry) entries.get(0);
    }

    /**
     * Set alert for SMS sending.
     */
    private static void sendDowntimeAlert(CalendarService myService,
            CalendarEntry entry, String title, String description,
            Long startOffset, Long endOffset, Long tzOffset)
            throws IOException, ServiceException {

        String postUrlString = entry.getLink("alternate",
                "application/atom+xml").getHref();

        URL postUrl = new URL(postUrlString);

        CalendarEventEntry myEntry = new CalendarEventEntry();
        myEntry.setTitle(new PlainTextConstruct(title));
        myEntry.setContent(new PlainTextConstruct(description));

        Date now = new Date();
        Date startDate = new Date(now.getTime() + startOffset);
        Date endDate = new Date(now.getTime() + endOffset);

        DateTime startTime = new DateTime(startDate.getTime() + tzOffset);
        DateTime endTime = new DateTime(endDate.getTime() + tzOffset);

        When eventTimes = new When();
        eventTimes.setStartTime(startTime);
        eventTimes.setEndTime(endTime);
        myEntry.addTime(eventTimes);

        // Send the request and receive the response:
        CalendarEventEntry insertedEntry = myService.insert(postUrl, myEntry);
        System.err.println("Got response for: "
                + insertedEntry.getTitle().getPlainText());
        for (When when : insertedEntry.getTimes()) {
            System.err.println("When: " + when.getStartTime() + " to "
                    + when.getEndTime());
        }

        // 5 minute reminder
        Reminder reminder = new Reminder();
        reminder.setMinutes(5);
        reminder.setMethod(Method.SMS);
        insertedEntry.getReminder().add(reminder);
        insertedEntry.update();
    }
}
